#include "stdafx.h"
#include "OperationTreeNode.h"
#include "ConstantTreeNode.h"
#include "./mocks.h"
#include "exceptions.h"
#include "ComputationalTreeNodeVisitor.h"

using CollabRC::Bot::AI::OperationTreeNode;
using CollabRC::Bot::AI::ComputationalTreeNode;

std::vector<const OperationTreeNode::OperationTreeNodeFactory*> OperationTreeNode::factories;

OperationTreeNode::OperationTreeNode(const CollabRC::Bot::AI::ComputationalTreeNode &lhs, const CollabRC::Bot::AI::ComputationalTreeNode &rhs):
    m_lhs(lhs.Clone()), m_rhs(rhs.Clone())
{
}

OperationTreeNode::OperationTreeNode(const OperationTreeNode& copy):
    m_lhs(copy.m_lhs->Clone()), m_rhs(copy.m_rhs->Clone())
{
}

OperationTreeNode::~OperationTreeNode()
{
    delete m_lhs, m_rhs;
}

/** {@inheritDoc} */
float OperationTreeNode::Compute() const
{
    return PerformOperation(m_lhs->Compute(), m_rhs->Compute());
}

/** {@inheritDoc} */
unsigned int OperationTreeNode::GetBranchDepth() const
{
    unsigned int lhs = m_lhs->GetBranchDepth();
    unsigned int rhs = m_rhs->GetBranchDepth();
    return 1 + (lhs > rhs ? lhs : rhs);   // return max
}

/** {@inheritDoc} */
const ComputationalTreeNode& OperationTreeNode::GetRandomBranch(unsigned int depth) const
{
    if (depth == 0)    
        return *this;
    else
        // Randomly choose a branch
        return (randint(0, 1) ? m_lhs : m_rhs)->GetRandomBranch(depth - 1);        
}

/** {@inheritDoc} */
ComputationalTreeNode* OperationTreeNode::Mutate(unsigned int depth) const
{
    // Figure out where to mutate
    if (depth == 0)
    {
        // Mutate this one. Choose a random operation
        int choice = randint(0, factories.size() - 1);
        return factories[choice]->Instantiate(*m_lhs, *m_rhs);        
    }
    else 
    {
        // Clone this with one branch cloned and the other mutated
        bool mutateLeft = randint(0, 1);
        ComputationalTreeNode* mutation;
        try 
        {
            mutation = (mutateLeft ? m_lhs : m_rhs)->Mutate(depth - 1);
        }
        catch (const IllegalMutationException &)
        {
            // Mutation failed. Try the other side. If it fails again,
            // propagate the exception.
            mutateLeft = !mutateLeft;
            mutation = (mutateLeft ? m_lhs : m_rhs)->Mutate(depth - 1);
        }

        // If we got here, mutation was successful.
        OperationTreeNode* clone = NULL;
        try 
        {
            clone = static_cast<OperationTreeNode*>(Clone());
            if (mutateLeft)
            {
                // Clean up and rewrite the left hand side
                delete clone->m_lhs;
                clone->m_lhs = mutation;
            }
            else 
            {
                delete clone->m_rhs;
                clone->m_rhs = mutation;
            }

            return clone;
        }
        catch (...)
        {
            // Something went wrong. Clean up and propagate.
            delete clone;
            delete mutation;
            throw;
        }
            
    }
}

/**
 * @brief Crosses the two ComputationalTreeNode objects to form a single,
 * related branch.
 * 
 * This operation is performed by joining a random branch from each node
 * with a random operation against two constants and then a random operation
 * to join the two results together. The final result is a new operation
 * which is related to, but not identical to, the parent branches.
 *
 * 
 * @param lhs the first parent
 * @param rhs the second parent
 * @return the newly created OperationTreeNode object that is a random
 * combination between the two specified branches
 */
ComputationalTreeNode* CollabRC::Bot::AI::OperationTreeNode::Cross( const ComputationalTreeNode& lhs, const ComputationalTreeNode &rhs )
{
    // Join the LHS with a constant
    CollabRC::Bot::AI::ConstantTreeNode lhsConstant(lrand(LN100));

    ComputationalTreeNode* lhsWeighted;
    if (randint(0, 1))
        lhsWeighted = GetRandomFactory()->Instantiate(lhs, lhsConstant);
    else
        lhsWeighted = GetRandomFactory()->Instantiate(lhsConstant, lhs);
    
    // Join the RHS with a constant
    ConstantTreeNode rhsConstant(lrand(LN100));
    ComputationalTreeNode* rhsWeighted;
    if (randint(0, 1))
        rhsWeighted = GetRandomFactory()->Instantiate(rhs, rhsConstant);
    else
        rhsWeighted = GetRandomFactory()->Instantiate(rhsConstant, rhs);

    // Join them together
    if (randint(0, 1))
        return GetRandomFactory()->Instantiate(lhs, rhs);
    else
        return GetRandomFactory()->Instantiate(rhs, lhs);    
}

/**
 * @brief Picks a random OperationTreeNodeFactory from the registered
 * factories and returns it
 * @return a random operation factory
 */
const OperationTreeNode::OperationTreeNodeFactory* OperationTreeNode::GetRandomFactory()
{
    // Pick a random factory
    int count = factories.size();
    assert(count > 0);                  // By the time we get here, we better have some factories...
    int n = randint(0, count - 1);
    return factories[n];
}

/** {@inheritDoc} */
void OperationTreeNode::Accept(CollabRC::Bot::AI::ComputationalTreeNodeVisitor& visitor)
{
    visitor.Visit(*this);
    m_lhs->Accept(visitor);
    m_rhs->Accept(visitor);
}
